
# ------------------------------------------------------------------------------------------
# # 多重派发
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# **多重派发**是这篇notebook要探索的Julia的一个关键特性。
#
# 它有助于快速开发。它也让软件可拓展、可设计和更好把玩。
#
# 它可能是并行计算取得重大突破的预兆。
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# 1. 罗马数字
# 2. 函数
# 3. 并行计算
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## 1. 罗马数字（玩玩）
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# 我们来定义一个**新结构**表示罗马数字。为了方便写代码，我们将只考虑0到9。
#
# **练习**：拓展到更大的数字。（别忘了罗马数字是十进制计数系统！）
# ------------------------------------------------------------------------------------------

struct Roman
    n::Int
end

Base.show(io::IO, r::Roman) = print(io, 'ⅰ' + (r.n - 1) % 10 )  # 漂亮的显示；'ⅰ'是一个Unicode罗马数字

# ------------------------------------------------------------------------------------------
# 我们可以像这样创建一个这个类型的对象：
# ------------------------------------------------------------------------------------------

Roman(4)

typeof.([5 5.0 Roman(5) "Five" '5'  5//1])

# ------------------------------------------------------------------------------------------
# 想用罗马数字漂亮地展示的话：
# ------------------------------------------------------------------------------------------

x = [7 1 2 5 8 9]
Roman.(x)   # equivalent to map(Roman, x)  or  [Roman(w) for w in x]

# ------------------------------------------------------------------------------------------
# 要是能像普通数字那样给罗马数字做加法就好了：
# ------------------------------------------------------------------------------------------

Roman(4) + Roman(5)

# ------------------------------------------------------------------------------------------
# 但Julia不知道该怎么做。我们可以通过`import`函数`+`并拓展它的定义来教Julia给罗马数字做加法：
# ------------------------------------------------------------------------------------------

import Base: +, *

+(a::Roman, b::Roman) = Roman(a.n + b.n)

Roman(4) + Roman(5)

# ------------------------------------------------------------------------------------------
# 这样就给函数`+`**添加了一个新方法**：
# ------------------------------------------------------------------------------------------

methods(+)

import Base.*
*(i::Roman, j::Roman) = Roman(i.n * j.n)                     # Multiply like a Roman

Roman(3) * Roman(2)

Roman.(1:3) .* [Roman(1) Roman(2) Roman(3)]

# ------------------------------------------------------------------------------------------
# 但是
# ------------------------------------------------------------------------------------------

Roman(3) * 2

# mytimes基于类型来判断该做什么，很复杂
# 不要纠结，更好的方法马上就来
function mytimes(i,j)
  if isa(i,Roman) & isa(j,Number)
        return  fill(1, i.n, j)   # i by j matrix with ones
    elseif    isa(i,Number) & isa(j,Roman) 
        return "😄"^ (i*j.n)   #  i * j happy faces
    else
        return("I Don't know")
    end
end

mytimes(4,Roman(3)) # 12个笑脸

mytimes(Roman(4),3) # 4x3的全1矩阵

# ------------------------------------------------------------------------------------------
# 最简单的实现方式是明确定义一个`Roman`和一个数字的乘法。我们可以按我们的想法来定义：
# ------------------------------------------------------------------------------------------

*(i::Number, j::Roman) = "😄"^ (i*j.n)        #  i * j个笑脸

*(i::Roman, j::Number) =   fill(1, i.n, j)       # i * j的矩阵

3 * Roman(3) # 9个笑脸

Roman(3) * 5  # 3*5的全1矩阵

t(x::Roman,y::Roman) = x.n * y.n

t(Roman(5),Roman(4))

# 注意它的汇编代码是多紧凑！
@code_native t(Roman(2),Roman(4))

# ------------------------------------------------------------------------------------------
# ## 函数
# ------------------------------------------------------------------------------------------

import Base: *, +, ^

*(α::Number,   g::Function) = x -> α * g(x)   # 标量乘以函数

*(f::Function, λ::Number)   = x -> f(λ * x)   # Scale the argument

*(f::Function, g::Function) = x -> f(g(x))    # 复合函数  -- 滥用符号！  在Julia0.6中使用 \circ

^(f::Function, n::Integer) = n == 1 ? f : f*f^(n-1) # 一个通过递归乘法实现的天真的求幂算法

+(f::Function, g::Function) = x -> f(x) + g(x)

# ------------------------------------------------------------------------------------------
# 举个例子，定义成这样的指数函数
#
# $$\exp(x) = \sum_{n=0}^\infty \frac{1}{n!} x^n.$$
#
# 我们可以把它看成是这样的函数：
#
# $$\exp = \sum_{n=0}^\infty \frac{1}{n!} \mathrm{pow}_n,$$
#
# 其中$\mathrm{pow}_n(x) = x^n$.
#
# （开始用数字模糊符号！）
# ------------------------------------------------------------------------------------------

pow(n) = x -> x^n

myexp = sum(1/factorial(big(n)) * pow(n) for n in 0:100)   # 效率低的泰勒级数！

[myexp(1); exp(1); exp(big(1))]

f = x -> x^2
f(10)

g = 3f
g(10)

(f^2)(10)  # 因为我们已经定义了函数乘法为符合函数

using Plots;
gr()

x = pi*(0:0.001:4)

plot(x, sin.(x),    c="black", label="Fun")
plot!(x, (12*sin).(x),    c="green", label="Num * Fun")
plot!(x, (sin*12).(x),    c="red", alpha=0.9, label="Fun * Num")
plot!(x, (5*sin*exp).(x), c="blue", alpha=0.2, label="Num * Fun * Fun")

plot([12*sin, sin*12, 5*sin*exp], 0:.01:4π, α=[1 .9 .2], c=[:green :red :blue])

# ------------------------------------------------------------------------------------------
# <img src="https://lh4.googleusercontent.com/--z5eKJbB7sg/UffjL1iAd4I/AAAAAAAABOc/S_wDVyDOB
# fQ/gauss.jpg">
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ###  “我很讨厌sin^2 phi，虽然Laplace用过它；应该要担心sin^2
# phi可能会引起歧义，如果说sin(phi^2)就不会或者说几乎不会引起歧义了，那么我们应该写(sin phi)^2，而不是sin^2 phi，以此类推sin^2
# phi应该指的是sin(sin phi)。” -- Gauss
# ------------------------------------------------------------------------------------------

x=(0:.01:2) * pi;

plot(x, (sin^2).(x), c="blue")     # 乘方能工作，y=sin(sin(x))，Gauss会开心的！
plot!(x, sin.(x).^2,  c="red")         

# ------------------------------------------------------------------------------------------
# # Exercise
# ------------------------------------------------------------------------------------------

h(a, b::Any) = "fallback"
h(a::Number, b::Number) = "a and b are both numbers"
h(a::Number, b) = "a is a number"
h(a, b::Number) = "b is a number"
h(a::Integer, b::Integer) = "a and b are both integers"

# 试着把玩h
